package sjtu.FaceRecognition;

import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;

public class ImgProcessor {
		
	private static final String TAG = "image processer";
	//private static final int COUNT_CLASSIFIER = 4;
	private static Scalar[] colors = {new Scalar(255, 0, 255), new Scalar(255, 0, 0), new Scalar(0, 255, 255)};
	public static final int FACE_COLOR_INDEX = 1;
	public static final int EYE_COLOR_INDEX = 1;
	public static final int POS_COLOR_INDEX = 2;
	/*private static final Size MAX_SIZE_FACE = new Size(240, 240);
	private static final Size MIN_SIZE_FACE = new Size(80, 80);
	private static final Size MAX_SIZE_EYE = new Size(40, 40);
	private static final Size MIN_SIZE_EYE = new Size(10, 10);*/
	
	
	//init for those in order to improve efficiency
	private static final Point PIC_CENTER = new Point(CommonUtil.PIC_WIDTH / 2.0f, CommonUtil.PIC_HEIGHT / 2.0f);
	private static final Mat ROTATION_MATRIX_90 = Imgproc.getRotationMatrix2D(PIC_CENTER, 360 - 90, 1.0);
	private static final Mat ROTATION_MATRIX_180 = Imgproc.getRotationMatrix2D(PIC_CENTER, 360 - 180, 1.0);
	private static final Mat ROTATION_MATRIX_270 = Imgproc.getRotationMatrix2D(PIC_CENTER, 360 - 270, 1.0);
	
	//private CascadeClassifier faceClassifiers[];
	//private Queue<Integer> availIndexQueue = new LinkedBlockingQueue<Integer>();
	
	private static ImgProcessor imgProcessor;
	
	public static ImgProcessor getInstance(Context context) {
		if (imgProcessor == null) {
			imgProcessor = new ImgProcessor(context);
		}
		return imgProcessor;
	}
	
	public void releaseResource() {
		ROTATION_MATRIX_90.release();
		ROTATION_MATRIX_180.release();
		ROTATION_MATRIX_270.release();
	}
	
	private ImgProcessor(Context context) {
		/*MediaStorageUtil.copyCascadeFile2Internal(context);
		//init several classifiers
		faceClassifiers = new CascadeClassifier[COUNT_CLASSIFIER];
		for (int i = 0; i < COUNT_CLASSIFIER; i++) {
			faceClassifiers[i] = new CascadeClassifier(
					CommonUtil.CASCADE_FILEPATHS[CommonUtil.CASCADE_FACE_INDEX]);
			availIndexQueue.offer(Integer.valueOf(i));
		}*/
	}
	
	
	
	/**
	 * convert jpeg byte array to gray bitmap, note that bitmapToMat and matToBitmap could
	 * only be supported for ARGB_8888 Bitmap and CV_8UC4 Mat
	 * @param data
	 * @param width
	 * @param height
	 * @return
	 */
	public Bitmap jpegByteArray2GrayBitmap(byte[] data) {
		Options o = new Options();
		o.inPreferredConfig = Bitmap.Config.ARGB_8888;
		Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length, o);
		Mat rgbaMat = Utils.bitmapToMat(bmp);
		
		Mat grayMat = new Mat();
		Imgproc.cvtColor(rgbaMat, grayMat, Imgproc.COLOR_RGBA2GRAY);
		
		//FindFeatures(grayMat.getNativeObjAddr(), rgbaMat.getNativeObjAddr());
		Mat result = new Mat();
		Imgproc.cvtColor(grayMat, result, Imgproc.COLOR_GRAY2RGBA, 4);
		Utils.matToBitmap(result, bmp);
		rgbaMat.release();
		grayMat.release();
		result.release();
		return bmp;
	}
	
	/**
	 * convert raw byte to rgba mat
	 * @param data
	 * @param width
	 * @param height
	 * @param local_detect, if true, do local face detect and draw ellipse
	 * @return the jpg Mat, note that it should be released!
	 */
	public Mat rawByteArray2RGBAMat(byte[] data, int width, int height, boolean local_detect, int NEW_WIDTH, int NEW_HEIGHT) {
		Mat originMat = new Mat(height + height / 2, width, CvType.CV_8UC1);
		originMat.put(0, 0, data);
		Mat rgbaMat = new Mat();
		Imgproc.cvtColor(originMat, rgbaMat, Imgproc.COLOR_YUV420sp2RGB, 4);
		if (NEW_WIDTH != width || NEW_HEIGHT != height) {
			Imgproc.resize(rgbaMat, rgbaMat, new Size(NEW_WIDTH, NEW_HEIGHT));
		}
		/*if (local_detect) {
			Imgproc.cvtColor(rgbaMat, originMat, Imgproc.COLOR_RGB2GRAY);
			TimeCalc tc = new TimeCalc();
			tc.setTime1();
			drawFaceIfExist(originMat, rgbaMat);
			tc.setTime2();
			if (CommonUtil.DEBUG_LOG) {
				Log.e(TAG, "drawface is " + tc.getTime());
			}
		}*/
		originMat.release();
		return rgbaMat;
	}
	
	/**
	 * rotate the mat
	 * @param mat
	 * @param rotation
	 * @param width
	 * @param height
	 * @return
	 */
	public Mat rotateMat(Mat mat, int rotation, int width, int height) {
		//rotation 0: no need to rotate
		if (rotation == 0 || rotation < 0) {
			return mat;
		}
		Size newSize;
		if (rotation == 90 || rotation == 270) {
			newSize = new Size(height, width);
		} else {
			newSize = new Size(width, height);
		}
		
		//Mat rotMat = getRotMatByRotation(rotation);
		Mat rotMat = Imgproc.getRotationMatrix2D(PIC_CENTER, 360 - 270, 1.0);
		if (rotMat == null) {
			return mat;
		}
		Mat newMat = new Mat(newSize, mat.type());
		try {
			Imgproc.warpAffine(mat, newMat, rotMat, newSize);
		} catch(Exception e) {
			newMat.release();
			return mat;
		}
		mat.release();
		return newMat;
	}
	
	private Mat getRotMatByRotation(int rotation) {
		if (rotation == 90) {
			return ROTATION_MATRIX_90;
		} else if (rotation == 180) {
			return ROTATION_MATRIX_180;
		} else if (rotation == 270) {
			return ROTATION_MATRIX_270;
		}
		return null;
	}
	
	/**
	 * convert mat to bitmap
	 * @param mat Note that the mat won't be released here
	 * @param width
	 * @param height
	 * @return
	 */
	public Bitmap mat2Bitmap(Mat mat, int width, int height) {
		Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
		if (Utils.matToBitmap(mat, bmp)) {
			return bmp;
		}
		bmp.recycle();
		return null;
	}
	
	//get index of available Classifier 
	/*private int getClassifierIndex() {
		synchronized (availIndexQueue) {
			Integer index;
			while ((index = availIndexQueue.poll()) == null) {
				try {
					availIndexQueue.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			return index.intValue();
		}
	}*/
	
	//return index of used Classifier
	/*private void returnClassifierIndex(int index) {
		synchronized (availIndexQueue) {
			availIndexQueue.offer(Integer.valueOf(index));
			availIndexQueue.notify();
		}
	}*/
	
	/*private boolean drawFaceIfExist(Mat grayMat, Mat rgbaMat) {
		List<Rect> faces = new ArrayList<Rect>();
		TimeCalc tc = new TimeCalc();
		Imgproc.equalizeHist(grayMat, grayMat);
		int index = getClassifierIndex();
		tc.setTime1();
		//more changes should be made here to improve efficiency
		faceClassifiers[index].detectMultiScale(grayMat, faces, 1.2, 2, 0, MIN_SIZE_FACE, MAX_SIZE_FACE);
		//faceClassifiers[index].detectMultiScale(grayMat, faces, 1.3, 2, 0, new Size(10, 10), new Size(40, 40));
		returnClassifierIndex(index);
		tc.setTime2();
		if (CommonUtil.DEBUG_LOG) {
			Log.e(TAG, "pic time calc is " + tc.getTime());
		}
		if (faces.size() > 0) {
			Rect rect = faces.get(0);
			Point center = new Point(rect.x + 0.5 * rect.width, rect.y + 0.5 * rect.height);
			Core.ellipse(rgbaMat, center, new Size(rect.width * 0.5,
					rect.height * 0.5), 0, 0, 360, colors[FACE_COLOR_INDEX], 1,
					8, 0);
		}
		return false;
	}*/
	
	public void drawEllipseOnImage(Mat image, int x, int y, int width, int height, int color_index) {
		Point center = new Point(x + 0.5 * width, y + 0.5 * height);
		Core.ellipse(image, center, new Size(0.5 * width, 0.5 * height), 0, 0, 360, colors[color_index], 2, 8, 0);
	}
	
	public void drawRectOnImage(Mat image, int x, int y, int width, int height, int color_index) {
		Core.rectangle(image, new Point(x, y), new Point(x + width, y + height), colors[color_index]);
	}
	
	public Bitmap rawByteArray2RGBABitmap2(byte[] data, int width, int height) {
		int frameSize = width * height;
        int[] rgba = new int[frameSize];

            for (int i = 0; i < height; i++)
                for (int j = 0; j < width; j++) {
                    int y = (0xff & ((int) data[i * width + j]));
                    int u = (0xff & ((int) data[frameSize + (i >> 1) * width + (j & ~1) + 0]));
                    int v = (0xff & ((int) data[frameSize + (i >> 1) * width + (j & ~1) + 1]));
                    y = y < 16 ? 16 : y;

                    int r = Math.round(1.164f * (y - 16) + 1.596f * (v - 128));
                    int g = Math.round(1.164f * (y - 16) - 0.813f * (v - 128) - 0.391f * (u - 128));
                    int b = Math.round(1.164f * (y - 16) + 2.018f * (u - 128));

                    r = r < 0 ? 0 : (r > 255 ? 255 : r);
                    g = g < 0 ? 0 : (g > 255 ? 255 : g);
                    b = b < 0 ? 0 : (b > 255 ? 255 : b);

                    rgba[i * width + j] = 0xff000000 + (b << 16) + (g << 8) + r;
                }

        Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        bmp.setPixels(rgba, 0 , width, 0, 0, width, height);
        return bmp;
	}
//	
//	public native void FindFeatures(long matAddrGr, long matAddrRgba);
//    
//    static {
//    	System.loadLibrary("face_recognition");
//    }
}
